﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Management; 

namespace CacheSharp.Core
{
    /// <summary>
    /// 简单字典缓存器
    /// </summary>
    internal class SimDicCacher : ICacher
    {
        #region  Variables and Properties

        /// <summary>
        /// 数据缓存字典
        /// </summary>
        private static Dictionary<string, Dictionary<string, object>> _dicObj;

        /// <summary>
        /// 过期时间缓存字典
        /// </summary>
        private static Dictionary<string, Dictionary<string, DateTime>> _dicDt;

        /// <summary>
        /// 默认过期时间(初始值为1天)
        /// </summary>
        private static TimeSpan _defaultExpireTime;

        /// <summary>
        /// 最短的存储时间
        /// </summary>
        private static readonly TimeSpan MinTs = new TimeSpan(0, 0, 0, 1, 0);

        /// <summary>
        /// 锁物件
        /// </summary>
        private static object _lock;

        /// <summary>
        /// 当前系统环境
        /// </summary>
        private static PlatformID _platformId;

        #endregion

        #region Constructors

        static SimDicCacher()
        {
            _dicObj = new Dictionary<string, Dictionary<string, object>>();
            _dicDt = new Dictionary<string, Dictionary<string, DateTime>>();
            _defaultExpireTime = TimeSpan.FromDays(1);
            _lock = new object();
            _platformId = Environment.OSVersion.Platform;
        }

        public SimDicCacher()
        {
        }

        #endregion

        #region IReader

        public bool Exist(string groupName, string key)
        {
            if (string.IsNullOrEmpty(groupName) || string.IsNullOrEmpty(key)) return false;
            if (!_dicDt.ContainsKey(groupName)) return false;

            var dic = _dicDt[groupName];
            return dic.ContainsKey(key);
        }

        public DateTime? GetExpireTime(string groupName, string key)
        {
            DateTime? dt = null;
            lock (_lock)
            {
                if (Exist(groupName, key))
                    dt = _dicDt[groupName][key];
            }
            return dt;
        }

        public object GetValue(string groupName, string key)
        {
            object obj = null;
            lock (_lock)
            {
                if (Exist(groupName, key))
                    obj = _dicObj[groupName][key];
            }
            return obj;
        }

        public string GetString(string groupName, string key)
        {
            string obj = null;
            lock (_lock)
            {
                if (Exist(groupName, key))
                    obj = _dicObj[groupName][key] as string;
            }
            return obj;
        }

        public T GetValue<T>(string groupName, string key)
        {
            T t = default(T);
            lock (_lock)
            {
                if (Exist(groupName, key))
                {
                    try
                    {
                        t = (T)_dicObj[groupName][key];
                    }   // ReSharper disable once EmptyGeneralCatchClause
                    catch {}
                }
            }
            return t;
        }

        public Dictionary<string, object> GetValues(string groupName, string keyPart)
        {
            Dictionary<string, object> dic = new Dictionary<string, object>();
            lock (_lock)
            {
                var keys = _dicObj.Keys.ToArray();
                foreach (string key in keys)
                {
                    if (Exist(groupName, key))
                    {
                        var value = GetValue(groupName, key);
                        dic.Add(key, value);
                    }
                }
            }
            return dic;
        }

        public object PopValue(string groupName, string key)
        {
            object obj = null;
            lock (_lock)
            {
                if (Exist(groupName, key))
                {
                    obj = _dicObj[groupName][key];
                    _dicDt[groupName].Remove(key);
                    _dicObj[groupName].Remove(key);
                }
            }
            return obj;
        }

        public string PopString(string groupName, string key)
        {
            string obj = null;
            lock (_lock)
            {
                if (Exist(groupName, key))
                {
                    obj = _dicObj[groupName][key] as string;
                    _dicDt[groupName].Remove(key);
                    _dicObj[groupName].Remove(key);
                }
            }
            return obj;
        }

        public T PopValue<T>(string groupName, string key)
        {
            T t = default(T);
            lock (_lock)
            {
                if (Exist(groupName, key))
                {
                    try
                    {
                        t = (T)_dicObj[groupName][key];
                        _dicDt[groupName].Remove(key);
                        _dicObj[groupName].Remove(key);
                    }   // ReSharper disable once EmptyGeneralCatchClause
                    catch { }
                }
            }
            return t;
        }

        #endregion

        #region IWriter

        public TimeSpan DefaultExpireTime
        {
            get { return _defaultExpireTime; }
            set { _defaultExpireTime = value < MinTs ? MinTs : value; }
        }

        /// <summary>
        /// 添加群组
        /// </summary>
        /// <param name="groupName">群组名</param>
        private static void AddGroup(string groupName)
        {
            if (_dicObj.ContainsKey(groupName)) return;

            Dictionary<string, object> dicObj = new Dictionary<string, object>();
            Dictionary<string, DateTime> dicDt = new Dictionary<string, DateTime>();
            _dicObj.Add(groupName, dicObj);
            _dicDt.Add(groupName, dicDt);
        }

        public bool SetValue(string groupName, string key, object value, DateTime expiry)
        {
            if (string.IsNullOrEmpty(groupName) || string.IsNullOrEmpty(key)) return false;
            if (expiry < DateTime.Now) expiry = DateTime.Now + TimeSpan.FromSeconds(1);

            lock (_lock)
            {
                if (!Exist(groupName, key)) AddGroup(groupName);
                if (_dicObj.ContainsKey(key))
                {
                    _dicObj[groupName][key] = value;
                    _dicDt[groupName][key] = expiry;
                }
                else
                {
                    _dicObj[groupName].Add(key, value);
                    _dicDt[groupName].Add(key, expiry);
                }
            }
            return true;
        }

        public bool SetValue(string groupName, string key, object value)
        {
            return SetValue(groupName, key, value, DateTime.Now + DefaultExpireTime);
        }

        public bool Remove(string groupName, string key)
        {
            if (!Exist(groupName, key)) return true;

            lock (_lock)
            {
                _dicObj[groupName].Remove(key);
                _dicDt[groupName].Remove(key);
            }
            return true;
        }

        public bool RemoveGroup(string groupName)
        {
            if (!_dicObj.ContainsKey(groupName)) return true;

            lock (_lock)
            {
                _dicObj[groupName].Clear();
                _dicObj.Remove(groupName);

                _dicDt[groupName].Clear();
                _dicDt.Remove(groupName);
            }
            return true;
        }

        public bool RemoveAll()
        {
            lock (_lock)
            {
                var groupNames = _dicObj.Keys.ToArray();
                int countGroup = _dicObj.Count;
                for (int i = 0; i < countGroup; i++)
                {
                    string groupName = groupNames[i];
                    _dicObj[groupName].Clear();
                    _dicObj.Remove(groupName);

                    _dicDt[groupName].Clear();
                    _dicDt.Remove(groupName);
                }

                _dicObj.Clear();
                _dicDt.Clear();
            }
            return true;
        }

        #endregion

        #region ICacher

        public string CacherName
        {
            get { return "SimDicCacher"; }
        }

        public int CachedNum()
        {
            int count = 0;
            lock (_lock)
            {
                if (_dicDt.Count != 0)
                {
                    count += _dicDt.Keys.Select(key => _dicDt[key]).Select(dic => dic.Count).Sum();
                }
            }
            return count;
        }

        public int CachedNum(string groupName)
        {
            if (!_dicDt.ContainsKey(groupName)) return 0;
            int count;
            lock (_lock)
            {
                Dictionary<string, DateTime> dic = _dicDt[groupName];
                count = dic.Count;
            }
            return count;
        }

        public void TryTrimAll()
        {
            if (ChechNeedTrim(_platformId))
            {
                DelExpire();
                GC.Collect();
            }
        }

        /// <summary>
        /// 检查内存是否占用过高
        /// </summary>
        /// <param name="platformId"></param>
        /// <returns></returns>
        private static bool ChechNeedTrim(PlatformID platformId)
        {
            bool needTrim = true;
            uint mb = 104*1024;

            if (platformId == PlatformID.Win32NT)
            {
                WinApiHelper.MEMORYSTATUSEX memStatus = new WinApiHelper.MEMORYSTATUSEX();
                if (WinApiHelper.GlobalMemoryStatusEx(memStatus))
                {
                    if (memStatus.dwMemoryLoad > 10 && memStatus.ullAvailPhys/mb > 512)
                        needTrim = false;
                }
            }
            else
            {
                ManagementClass mc = new ManagementClass("Win32_OperatingSystem");
                ManagementObjectCollection moc = mc.GetInstances();

                foreach (var o in moc)
                {
                    var mo = (ManagementObject) o;
                    if (mo["TotalVisibleMemorySize"] != null && mo["FreePhysicalMemory"] != null)
                    {
                        long totalBytes = long.Parse(mo["TotalVisibleMemorySize"].ToString());
                        long availableBytes = long.Parse(mo["FreePhysicalMemory"].ToString());
                        double availablePer = 100.0*availableBytes/totalBytes;
                        if (availablePer > 10 && availableBytes/mb > 512)
                            needTrim = false;
                    }
                }
            }
            return needTrim;
        }

        public void DelExpire()
        {
            DateTime now = DateTime.Now;
            lock (_lock)
            {
                var groupNames = _dicObj.Keys.ToArray();
                int countGroup = _dicObj.Count;
                for (int i = 0; i < countGroup; i++)
                {
                    string groupName = groupNames[i];
                    var keys = _dicObj[groupName].Keys.ToArray();
                    int countKeys = _dicObj[groupName].Count;
                    for (int j = 0; j < countKeys; j++)
                    {
                        string key = keys[i];
                        DateTime dt = _dicDt[groupName][key];
                        if (dt < now)
                        {
                            _dicObj[groupName].Remove(key);
                            _dicDt[groupName].Remove(key);
                        }
                    }
                }
            }
        }

        #endregion

        #region IDispose

        public void Dispose()
        {
            if (_dicObj != null && _dicDt != null) RemoveAll();

            if (_dicObj != null)
            {
                _dicObj.Clear();
                _dicObj = null;
            }
            if (_dicDt != null)
            {
                _dicDt.Clear();
                _dicDt = null;
            }
        }

        #endregion
    }
}
